package compiler

// test

import (
	"fmt"
	"log"
	"sort"

	"gitee.com/chuanjinge/go-interpreter-waiig/src/monkey/ast"
	"gitee.com/chuanjinge/go-interpreter-waiig/src/monkey/code"
	"gitee.com/chuanjinge/go-interpreter-waiig/src/monkey/object"
)

// 编译器
// 拥有一个常量池和一个指令集合
type Compiler struct {
	instructions code.Instructions // 指令集
	constants    []object.Object   // 常量池，那些可以在编译阶段就确定了值得对象最终都被安置在此处。
	symbolTable  *SymbolTable      // 符号表，赋值语句生成的变量（在执行赋值语句之前没有值，之后的表达式中使用的时候有值）

	// 最近一条输出的指令和倒数第二条输出的指令，用来回滚指令使用
	// lastInstruction     EmittedInstructions
	// previousInstruction EmittedInstructions
	// 将如上的指令统计绑定到作用域中，作用域用栈的方式堆积，如下scopes就是栈
	scopes     []CompilationScope
	scopeIndex int // 指向栈顶
}

// 表征已输出的操作指令
type EmittedInstructions struct {
	Opcode   code.Opcode // 该元素表示指令的操作码，看来并没有存储完整的指令
	Position int         // 指令在指令字节码中的字节索引
}

// 作用域 - 用来标识当前指令的影响范围，比如函数体的指令集的影响范围只能是函数体内部
type CompilationScope struct {
	instructions        code.Instructions
	lastInstruction     EmittedInstructions // 这个输出的操作指令的偏移量是以当前作用域的指令集为准的
	previousInstruction EmittedInstructions // 这个输出的操作指令的偏移量是以当前作用域的指令集为准的
}

func New() *Compiler {
	mainScope := CompilationScope{
		instructions:        code.Instructions{},
		lastInstruction:     EmittedInstructions{},
		previousInstruction: EmittedInstructions{},
	}

	return &Compiler{
		instructions: code.Instructions{},
		constants:    []object.Object{},
		// lastInstruction:     EmittedInstructions{}, // 为什么使用的是变量，而非指针呢，好像指针也没用，因为就只有这一个地方用!!!
		// previousInstruction: EmittedInstructions{},
		scopes:      []CompilationScope{mainScope},
		scopeIndex:  0,
		symbolTable: NewSymbolTable(),
	}
}

func NewWithState(symbolTable *SymbolTable, constants []object.Object) *Compiler {
	c := New()
	c.symbolTable = symbolTable
	c.constants = constants
	return c
}

// !+scope
// 代码块只有两个操作：进入和离开，不会发生跳跃，必须是这种顺序操作。
// 进入一个新的代码块
func (c *Compiler) enterScope() {
	scope := CompilationScope{
		instructions:        code.Instructions{},
		lastInstruction:     EmittedInstructions{},
		previousInstruction: EmittedInstructions{},
	}

	c.scopes = append(c.scopes, scope)
	c.scopeIndex++
}

// 离开当前代码块，并返回该代码块的上下文
func (c *Compiler) leaveScope() code.Instructions {
	nowscope := c.currentInstructions()

	c.scopes = c.scopes[:len(c.scopes)-1] // 将代码块长度减一，相当于弹栈
	c.scopeIndex--

	return nowscope
}

// !-scope

// 编译的对象是基于 lexer/parser 最终形成的抽象语法树的节点
func (c *Compiler) Compile(node ast.Node) error {
	switch node := node.(type) {
	case *ast.Program:
		// 根节点包含多个语句，遍历处理
		for _, s := range node.Statements {
			err := c.Compile(s) // 遍历的过程就是将抽象语法树节点转为字节码写入 Compiler 常量池的过程
			if err != nil {
				return err
			}
		}
	case *ast.ExpressionStatement:
		err := c.Compile(node.Expression)
		if err != nil {
			return err
		}
		// 表达式语句在编译结束后实际上不需要编译对象，实际上let语句才需要，所以此处我们将该编译对象清理掉
		// 将清理栈顶元素的指令写入字节码指令集中，这样顺序执行到此处的时候就清理掉了
		c.emit(code.OpPop)
	case *ast.InfixExpression:
		if node.Operator != "<" {
			// 中缀表达式，左右表达式分别编译
			err := c.Compile(node.Left)
			if err != nil {
				return err
			}
			err = c.Compile(node.Right)
			if err != nil {
				return err
			}
		} else {
			// 当操作符是小于号的时候，先编译右边，再编译左边，为了将比较操作统一为大于号。
			// 此处纯粹是一个炫技操作，为了展示编译器可以更改编译顺序，从而使得在语法结构树上不同的结构可以统一为相同的表现形式。
			err := c.Compile(node.Right)
			if err != nil {
				return err
			}
			err = c.Compile(node.Left)
			if err != nil {
				return err
			}
			// node.Operator = ">" // 将操作符变更为大于号
			c.emit(code.OpGreaterThan)
			return nil
		}

		// 左右处理结束后，还要将中缀操作符输出到指令集中
		switch node.Operator {
		case "+":
			c.emit(code.OpAdd)
		case "-":
			c.emit(code.OpSub)
		case "*":
			c.emit(code.OpMul)
		case "/":
			c.emit(code.OpDiv)
		case ">":
			c.emit(code.OpGreaterThan)
		case "==":
			c.emit(code.OpEqual)
		case "!=":
			c.emit(code.OpNotEqual)
		default:
			return fmt.Errorf("unknown inflix operator when compile: %s", node.Operator)
		}
	case *ast.IntegerLiteral:
		// 既然已经处理到了字符串字面量，就直接以待处理对象的形式来响应吧
		intObj := &object.Integer{Value: node.Value}
		// 将该对象添加到常量池中
		consPos := c.addConstants(intObj)
		c.emit(code.OpConstant, consPos)
	case *ast.StringLiteral:
		// 语法树上的字符串字面量节点，在编译为字节码的过程中直接用字符串对象存储到常量池中
		strObj := &object.String{Value: node.Value}
		consPos := c.addConstants(strObj)
		c.emit(code.OpConstant, consPos)
	case *ast.BoolLiteral:
		if node.Value {
			c.emit(code.OpTrue)
		} else {
			c.emit(code.OpFalse)
		}
	case *ast.PrefixExpression:
		// 前缀表达式，如何编译为字节码呢？
		// 输出运算对象到字节码中，再输出运算符到字节码中
		err := c.Compile(node.Right)
		if err != nil {
			return err
		}
		switch node.Operator {
		case "!":
			c.emit(code.OpBang)
		case "-":
			c.emit(code.OpMinus)
		default:
			return fmt.Errorf("unknown prefix operator when compile: %s", node.Operator)
		}
	case *ast.IfExpression:
		// if条件语句编译为字节码，需要增加跳转操作码
		// condition的编译
		// -- node.Condition是一个具体的expression，比如 inflixExpression，而不是 expressionStatement,因为只有单独成行的表达式才构成 expressionstatement, 比如单独一行inflixExpression,或者单独一行ifExpression,这两种表达式内部包含的表达式都是具体的表达式。
		// -- 所以这个Compile不会出现 code.OpPop 的指令
		err := c.Compile(node.Condition)
		if err != nil {
			return err
		}
		// jumpIfNotTruthy 跳转到指定的偏移量位置
		// 这个偏移量暂时还不能确定，因为得等到 node.Consequence 的字节码确定之后
		offset := 0
		pos := c.emit(code.OpJumpNotTruthy, offset) // @todo 随后要修改这个offset的值
		// Consequence 的编译是包含 expressionStatement 的，因为其中可能有多行的单独成行的 inflixExpression 等，所以最后可能会存在 code.OpPop
		// 但是因为我们定义了一个标准：最后一条语句的结果作为block块的结果，所以最后的这个 code.OpPop 操作码我们不应该执行。
		// 为了统一逻辑，我们仍然执行，但是在执行完后如果最后一条是OpPop，则将该指令从指令集中清除。相当于一个回滚操作!!!
		err = c.Compile(node.Consequence)
		if err != nil {
			return err
		}

		// 如果最后一条是OpPop的话则应该回滚该指令
		// 回滚方法：
		// -- 我们让编译器持有最近一条输出的指令（包括该指令的字节位置索引）
		// -- 这样当我们需要回滚的时候，我们就将指令集截取一下到最近一条的起始索引处即可。
		// ---- 当然如果我们真的回滚了，就意味着【新的[最近一条输出的指令]】发生了变化，我们得重新指向，为了方便只想，我们还要保存倒数第二条输出的指令。
		if c.lastInstructionIsPop() {
			c.removeLastPop()
		}

		// 开始修改 offset 的值
		// offset的值应该是当前字节码的最后一个有效索引的下一个索引
		offset = len(c.currentInstructions())
		c.changeOperand(pos, offset)

		// 强制跳转指令如果设置了，那么[为否跳转指令】的偏移量就要移动到该指令后了
		pos2 := c.emit(code.OpJump, 0) // 此处的0是一个暂代数据，后续会变更他
		offset = len(c.currentInstructions())
		c.changeOperand(pos, offset)

		// 看看有没有else，也就是备用语句块
		if node.Alternative != nil {
			// 编译备用语句块
			err := c.Compile(node.Alternative)
			if err != nil {
				return err
			}
			if c.lastInstructionIsPop() {
				c.removeLastPop()
			}
		} else {
			c.emit(code.OpNull)
		}
		// 重置偏移量
		offset = len(c.currentInstructions())
		log.Printf("~~~~~offset: %d\n", offset)
		c.changeOperand(pos2, offset)
	case *ast.BlockStatement:
		// 语句块的编译，if 和 else 会用到语句块
		// 逐条语句编译
		log.Println("~~~~~BlockStatement:", node)
		for _, stmt := range node.Statements {
			err := c.Compile(stmt)
			if err != nil {
				return err
			}
		}
	case *ast.LetStatement:
		// let 语句是要赋值的
		// 表达式编译为字节码
		err := c.Compile(node.Value)
		if err != nil {
			return err
		}
		// 标识符作为符号表的一份子写入
		newSymbol := c.symbolTable.Define(node.Name.Value)
		c.emit(code.OpSetGlobal, newSymbol.Index)
	case *ast.Identifier:
		symbol, ok := c.symbolTable.Resolve(node.Value)
		if !ok {
			return fmt.Errorf("identifier cannot find: %s", node.Value)
		}
		c.emit(code.OpGetGlobal, symbol.Index)
	case *ast.Array:
		for _, item := range node.Elements {
			err := c.Compile(item)
			if err != nil {
				return err
			}
		}
		c.emit(code.OpArray, len(node.Elements))
	case *ast.HashLiteral:
		// map的遍历在golang中是随机的，这导致我们无法执行测试用例
		// 所以此处我们将key进行一下基于ascii码的排序，然后基于key的顺序遍历

		keys := []ast.Expression{}
		for k := range node.Pairs {
			keys = append(keys, k)
		}

		// sort一下，sort规则为基于ascii码的从小到大的顺序
		sort.Slice(keys, func(i, j int) bool {
			return keys[i].String() < keys[j].String()
		})

		for _, key := range keys {
			err := c.Compile(key)
			if err != nil {
				return err
			}
			err = c.Compile(node.Pairs[key])
			if err != nil {
				return err
			}
		}

		c.emit(code.OpHash, len(node.Pairs)*2)
	case *ast.IndexExpression:
		err := c.Compile(node.Name)
		if err != nil {
			return err
		}
		err = c.Compile(node.Index)
		if err != nil {
			return err
		}
		c.emit(code.OpIndex)
	case *ast.FunctionalLiteral:
		// 当出现一个新的函数体的时候，就意味着进入了一个新的代码块
		c.enterScope()
		// 编译函数体，其实和if一样，都是blockstatement
		err := c.Compile(node.Body)
		if err != nil {
			return err
		}

		// 如果当前代码块的最后一个指令是 OpPop 意味着最后没有return,我们干掉OpPop指令改为OpReturnValue指令
		if c.lastInstructionIsPop() {
			c.replaceLastPopWithReturn()
		}

		// 正常情况下编译结束之后是有一个 returnValue 指令的，如果没有说明是个空的，此时我们增加return指令
		if !c.lastInstructionIs(code.OpReturnValue) {
			log.Println("~~~~~c.lastInstructionIs: ", c.scopes[c.scopeIndex].lastInstruction.Opcode)
			c.emit(code.OpReturn)
		}

		// 现在字节码被分成了两个代码块，在 c.scopes 中有两坨。
		// 我们针对最新的这一坨可以进行计算了，当我们离开代码块的时候，就能得到具体的字节码
		newscopeIns := c.leaveScope() // leaveScope 是一个离开操作，但是会顺手把该scope的指令一下都拽出来，并干掉当前scope，于是我们就可以一睹芳容这些指令集，并进行操作了
		// 目前来看我们拿到函数体的指令集后，可以进行的操作就是将其作为一个常量塞入到常量池中，然后在字节码中标识出来这里有一个函数字面量
		compiledFn := &object.CompiledFunction{Instructions: newscopeIns}
		c.emit(code.OpConstant, c.addConstants(compiledFn))
	case *ast.ReturnStatement:
		err := c.Compile(node.Value)
		if err != nil {
			return err
		}
		c.emit(code.OpReturnValue)
	case *ast.CallExpression:
		// 函数调用
		// 左边一半是标识符，编译它，会触发他以code.OpGetGlobal的操作码进入字节码中
		err := c.Compile(node.Function)
		if err != nil {
			return err
		}

		// 然后输出执行函数的字节码，一遍虚拟机可以根据该字节码弹栈执行上边指定的常量池中的函数字面量
		c.emit(code.OpCall)
	}

	return nil
}

// 将目标对象添加到常量池中
// 返回该对象的所处位置索引
func (c *Compiler) addConstants(obj object.Object) int {
	c.constants = append(c.constants, obj)
	return len(c.constants) - 1
}

// 将操作数输出到字节码指令集中
// 返回值为当前 操作码+操作数 在字节码指令集中的字节位置索引
func (c *Compiler) emit(op code.Opcode, operands ...int) int {
	// 构建字节码
	ins := code.Make(op, operands...)
	// 将字节码追加进入指令集合中
	pos := c.addInstructions(ins)
	// 记录最近一条输出的操作码和他的位置
	c.setLastInstruction(op, pos)

	return pos
}

// 将已经整理好的字节码追加到指令集中
// 返回值为当前指令码在指令集中的字节位置索引起始位置
func (c *Compiler) addInstructions(ins []byte) int {
	// -- 第一版是没有代码块概念的，直接对全局的指令集合进行追加即可
	// pos := len(c.instructions)
	// c.instructions = append(c.instructions, ins...)
	// return pos
	// -- 第二版增加代码块概念，追加操作仅针对当前栈顶的代码块
	posNowScope := len(c.currentInstructions())
	updateNowScopeIns := append(c.currentInstructions(), ins...)
	c.scopes[c.scopeIndex].instructions = updateNowScopeIns
	return posNowScope
}

// 获取当前栈顶的代码块（包含作用域的指令集合）
func (c *Compiler) currentInstructions() code.Instructions {
	return c.scopes[c.scopeIndex].instructions
}

// 修改操作数-有些操作码+操作数的结构会把操作数重新修正
// opPos 表示字节码索引位置
// operand 表示操作数
func (c *Compiler) changeOperand(opPos int, operand int) {
	// -- v1 针对无代码块
	// -- v2 针对当前代码块
	nowscope := c.scopes[c.scopeIndex]
	op := code.Opcode(nowscope.instructions[opPos])
	newInstruction := code.Make(op, operand)

	for i := 0; i < len(newInstruction); i++ {
		c.scopes[c.scopeIndex].instructions[opPos+i] = newInstruction[i]
	}
}

// 替换Pop指令为ReturnValue指令
func (c *Compiler) replaceLastPopWithReturn() {
	nowscope := c.scopes[c.scopeIndex]
	nowscopeInsPos := nowscope.lastInstruction.Position // 当前代码块的最后一个指令所处的字节索引位置

	newIns := code.Make(code.OpReturnValue) // 这是个单字节指令
	c.scopes[c.scopeIndex].instructions[nowscopeInsPos] = newIns[0]
	// 修改统计数据
	c.scopes[c.scopeIndex].lastInstruction.Opcode = code.OpReturnValue
}

// 记录最近一条输出的指令
func (c *Compiler) setLastInstruction(op code.Opcode, pos int) {
	// -- 第一版没有代码块概念
	// prev := c.lastInstruction
	// last := EmittedInstructions{Opcode: op, Position: pos}
	// c.previousInstruction = prev
	// c.lastInstruction = last
	// -- 第二版针对代码块操作，每次都操作栈顶代码块
	nowscope := c.scopes[c.scopeIndex]
	lastIns := EmittedInstructions{Opcode: op, Position: pos}
	prevIns := nowscope.lastInstruction

	c.scopes[c.scopeIndex].previousInstruction = prevIns
	c.scopes[c.scopeIndex].lastInstruction = lastIns
}

// 最近一条输出的指令是否是 OpPop
func (c *Compiler) lastInstructionIsPop() bool {
	nowscope := c.scopes[c.scopeIndex]
	return nowscope.lastInstruction.Opcode == code.OpPop
}

// 判断最后一条输出的指令是否是指定入参的指令
func (c *Compiler) lastInstructionIs(op code.Opcode) bool {
	nowscope := c.scopes[c.scopeIndex]
	return nowscope.lastInstruction.Opcode == op
}

// 回滚 OpPop 指令
func (c *Compiler) removeLastPop() {
	// -- 第一版 针对无代码块的操作
	// c.instructions = c.instructions[:c.lastInstruction.Position]
	// c.scopes[c.scopeIndex].lastInstruction = c.previousInstruction

	// -- 第二版 针对当前代码块的操作
	nowscope := c.scopes[c.scopeIndex]
	prevIns := nowscope.previousInstruction

	c.scopes[c.scopeIndex].instructions = nowscope.instructions[:nowscope.lastInstruction.Position]
	c.scopes[c.scopeIndex].lastInstruction = prevIns
}

// 字节码指令集
// -- 每一个编译器都会最终生成一个字节码指令集
// -- 该字节码指令集会被初始化虚拟机使用，其中除了字节码，此外还携带了常量池和符号表
type Bytecode struct {
	Instructions code.Instructions
	Constants    []object.Object
	SymbolTable  *SymbolTable
}

// 返回最终生成的字节码指令集合
// 其实就是 Compile 方法最终生成的指令集合和常量池
func (c *Compiler) Bytecode() *Bytecode {
	return &Bytecode{
		Instructions: c.currentInstructions(),
		Constants:    c.constants,
		SymbolTable:  c.symbolTable,
	}
}
